/**
 * Improved Cube UI demonstration in JavaFX
 * @author charmgil, pmwalk
 */

package cube;

import javafx.util.Math;
import javafx.scene.CustomNode;
import javafx.scene.paint.Color;

import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.media.MediaView;
import javafx.scene.media.Media;
import javafx.stage.Alert;
import javafx.scene.Group;
import javafx.scene.shape.Polygon;

import javafx.scene.Cursor;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;

import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.media.MediaPlayer;


/**
 * @author charmgil
 */


// Color Scheme
var LIGHTBLUE = Color.web( "#7ba4cb" );
var BLUE = Color.web( "#3b3396" );
var WHITE = Color.web( "#ffffff" );
var BLACK = Color.web( "#000000" );


// Config
package var FACE_WIDTH = 150;
package var FACE_HEIGHT = 150;

var r = bind Layout.icW;
var h = bind Layout.icH;
var d = bind if( h > r ) h - r else r - h;


// acceleration
var turn_slowdown_threshold = bind Layout.brk1_range;
var turn_slowdown2_threshold = bind Layout.brk2_range;
var turn_stop_threshold = bind Layout.stop_range;

var turn_slowdown_ratio = bind Layout.brk1_ratio;
var turn_slowdown2_ratio = bind Layout.brk2_ratio;

def max_acc = 0.1;

package var mode = bind Layout.mode;      // 1: init, 2: running


// flags
package var frontface = 1;
var isDragging = false;


// coordination calc
var ay = 0.000;
def cy = bind Math.cos( ax );
def sy = bind Math.sin( ax );

package var ax = -0.000;
def cx = bind Math.cos( ay );
def sx = bind Math.sin( ay );

def ful = Point {   // front upper left
            x: -r
            y: -r
            z: r
        }
def fur = Point {   // front upper right
            x: r
            y: -r
            z: r
        }
def flr = Point {   // front lower right
            x: r
            y: r
            z: r
        }
def fll = Point {   // front lower left
            x: -r
            y: r
            z: r
        }
def bul = Point {   // back upper left
            x: bind -ful.x
            y: bind -ful.y
            z: bind -ful.z
        }
def bur = Point {   // back upper right
            x: bind -fur.x
            y: bind -fur.y
            z: bind -fur.z
        }
def blr = Point {   // back lower right
            x: bind -flr.x
            y: bind -flr.y
            z: bind -flr.z
        }
def bll = Point {   // back lower left
            x: bind -fll.x
            y: bind -fll.y
            z: bind -fll.z
        }

package def f1 = Face {
            number: 1
            ul: ful
            ur: fur
            lr: flr
            ll: fll
        }
package def f2 = Face {
            number: 2
            ul: fur //flr
            ur: bll //fur
            lr: bul //bll
            ll: flr //bul
        }
package def f3 = Face {
            number: 0
            ul: bur
            ur: fll
            lr: flr
            ll: bul
        }
package def f4 = Face {
            number: 0
            ul: bll
            ur: fur
            lr: ful
            ll: blr
        }
package def f5 = Face {
            number: 4
            ul: blr //bur
            ur: ful //blr
            lr: fll //ful
            ll: bur //fll
        }
package def f6 = Face {
            number: 3
            ul: bll //bur
            ur: blr //bul
            lr: bur //bll
            ll: bul //blr
        }
package def f7 = CubeExt {};

package def cube = [ f1, f2, f3, f4, f5, f6 ];

// Face: skeleton taken from VideoCube by Sergey A. Malenkov
package class Face extends CustomNode {

    var number: Integer;
    var ul: Point;  // upper left
    var ur: Point;  // upper right
    var lr: Point;  // lower right
    var ll: Point;  // lower left
    var error = true;
    def z = bind ul.z + ur.z + lr.z + ll.z;
    var start: Number;
    var length: Number;
    def animation = Timeline {
                repeatCount: Timeline.INDEFINITE
                keyFrames: [
                    at (0s) {start => 90; length => 0},
                    at (1s) {start => 90; length => 360},
                    at (2s) {start => 450; length => 0}
                ]
    }
    def animate = bind z > 0 and error on replace old {
                if( animate != old ){
                    if( animate ){ animation.play() }
                    else{ animation.pause() }
                }
    }
    var player:MediaView;
    var vdo:Media;
    package var target: String on replace old {
        if( target != old ){
            error = false;

            vdo = Media {
                source: target
                onError:function( error ){
                    this.error = true;
                    Alert.inform( "Loading error", error.message );
                }
            }
        }
    }
    package var targetName;
    package var targetType;
    package var targetLabelDisplay: Boolean = false;
    package var targetLabel: FaceLabel;

    override function create() {
        var adjX = bind if( Layout.obW > Layout.obH ) ( Layout.obW - Layout.obH ) / 2 else 0;
        var adjY = bind if( Layout.obH > Layout.obW ) ( Layout.obH - Layout.obW ) / 2 else 0;

        def pt = PerspectiveTransform {
                    ulx: bind ul.x + Layout.icOffsetX + h + adjX - 2*d;
                    uly: bind ul.y + Layout.icOffsetY + r + adjY;
                    urx: bind ur.x + Layout.icOffsetX + h + adjX;
                    ury: bind ur.y + Layout.icOffsetY + r + adjY;
                    lrx: bind lr.x + Layout.icOffsetX + h + adjX;
                    lry: bind lr.y + Layout.icOffsetY + r + adjY;
                    llx: bind ll.x + Layout.icOffsetX + h + adjX - 2*d;   // only suppose h > r
                    lly: bind ll.y + Layout.icOffsetY + r + adjY;
                }

        Group {
            visible: bind z > 0
            content: [
                Polygon {
                    points: bind [
                        pt.ulx, pt.uly,
                        pt.urx, pt.ury,
                        pt.lrx, pt.lry,
                        pt.llx, pt.lly
                    ]
                    fill: bind WHITE
                    stroke: bind BLACK
                    strokeWidth: 1

                    cursor: Cursor.HAND
                    blocksMouse: true

                    onMouseClicked: function(event) {
                        ax = 0.0;
                        ay = 0.0;


                        // application load: right click for a file chooser
                        if( event.button == MouseButton.SECONDARY ){
                            var fc: javax.swing.JFileChooser;

                            // item open
                            fc = Utils.getFileChooser( true );
                            if( javax.swing.JFileChooser.APPROVE_OPTION == fc.showOpenDialog( null ) ){
                               target = fc.getSelectedFile().toURI().toString();

                               var s = target.lastIndexOf( "/" );
                               var t = target.lastIndexOf( "." );

                               if( s == -1 )    s = 0;
                               
                               targetName = if( t == -1 ) target.subSequence( s + 1, target.length() ) else target.subSequence( s + 1, t );
                               targetType = if( t == -1 ) "UNKNOWN" else target.toUpperCase().subSequence( t + 1, target.length() );

                               print( targetName );
                               println( " [{targetType}]" );

                               if( targetName != null ){
                                   targetLabelDisplay = true;
                                   targetLabel.showTip.playFromStart();

                                   //println( "e:{error}, t:{targetLabelDisplay}" );
                               }

                               
                            }
                            return;
                        }


                        // double click - basic click action
                        if( Timer.isChecked ){
                            Timer.isChecked = false;
                            Timer.clickedTimeLine.stop();
                            // action
                            runCube();
                        }else{
                            Timer.clickedTimeLine.playFromStart();
                        }
                    }

                    onMouseDragged: function(event) {
                        if( Math.abs( event.dragX ) > Math.abs( event.dragY ) ){
                            ax = if ( Math.abs( event.dragX ) < 5 ) then 0 else event.dragX / 1000;
                            if( Math.abs( ax ) > max_acc ){
                                ax = ax / Math.abs( ax ) * max_acc;
                            }
                        }
                        isDragging = true;
                    }
                    onMouseReleased: function(e: MouseEvent): Void {
                        isDragging = false;
                    }
                },
                Group {
                    effect: pt
                    visible: bind error
                    content: [
                        Text {
                            effect: pt
                            x: bind Layout.offsetX
                            y: bind Layout.offsetY
                            content: bind "{number}"
                            fill: bind Layout.textColor;
                            font: Font {
                                size: 200
                                embolden: true
                            }
                        }
                    ]
                },
                player = MediaView {
                    effect: pt
                    visible: bind not error
                    mediaPlayer:MediaPlayer {
                        media: bind vdo
                        autoPlay: false
                        repeatCount: MediaPlayer.REPEAT_FOREVER
                        volume: 0
                    }
                },
                targetLabel = FaceLabel{
                    labeltext: bind targetName as String
                    show: bind targetLabelDisplay
                }

            ]
        }
    }

    function play(){
        if( vdo != null ){
            player.mediaPlayer.play();
            player.mediaPlayer.mute = true;
            targetLabelDisplay = false;
        }
    }
}

// animator taken from VideoCube by Sergey A. Malenkov
package def rotation = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames: KeyFrame {
        time: 20ms
        action: function() {
            if( ay != 0 ){
                ful.rotateX( cx, sx );
                fur.rotateX( cx, sx );
                flr.rotateX( cx, sx );
                fll.rotateX( cx, sx );
            }
            if( ax != 0 ){
                ful.rotateY( cy, sy );
                fur.rotateY( cy, sy );
                flr.rotateY( cy, sy );
                fll.rotateY( cy, sy );
            }

            var max = closestZmagnitude();
            //println( max );
            //println( ax );
            if( isDragging == false ){
                if( 4*r - max < turn_stop_threshold or max < 100*turn_stop_threshold  ){  // stop criteria
                    ax = 0;
                    ay = 0;
                    magnetFace();
                }else if( 4*r - max < turn_slowdown2_threshold or max < 10*turn_slowdown2_threshold ){
                    if( ax != 0 and Math.abs( ax ) > 0.01 )
                        ax = ax * turn_slowdown2_ratio;
                    if( ay != 0 and Math.abs( ay ) > 0.01 )
                        ay = ay * turn_slowdown2_ratio;
                    //print( "break2: " );
                    //println( ax );
                }else if( 4*r - max < turn_slowdown_threshold or max < 10*turn_slowdown_threshold ){
                    if( ax != 0 and Math.abs( ax ) > 0.05 )
                        ax = ax * turn_slowdown_ratio;
                    if( ay != 0 and Math.abs( ay ) > 0.05 )
                        ay = ay * turn_slowdown_ratio;

                    if( ax != 0 and Math.abs( ax ) > 0.001 )
                        ax = ax * turn_slowdown_ratio;
                    if( ay != 0 and Math.abs( ay ) > 0.001 )
                        ay = ay * turn_slowdown_ratio;

                    //print( "break1: " );
                    //println( ax );
                }
            }//if( isDragging == false )

            //println( "{Layout.icW},{Layout.icH}" );
            //println( "{f1.error},{f1.targetLabelDisplay}" );
        }// function action()
    }//keyframe
}//rotation

// adjust the projection + retrieve the front face number
function magnetFace(){
    ful.x = ful.x / Math.abs( ful.x ) * r;
    ful.y = ful.y / Math.abs( ful.y ) * r;
    ful.z = ful.z / Math.abs( ful.z ) * r;

    fur.x = fur.x / Math.abs( fur.x ) * r;
    fur.y = fur.y / Math.abs( fur.y ) * r;
    fur.z = fur.z / Math.abs( fur.z ) * r;

    fll.x = fll.x / Math.abs( fll.x ) * r;
    fll.y = fll.y / Math.abs( fll.y ) * r;
    fll.z = fll.z / Math.abs( fll.z ) * r;

    flr.x = flr.x / Math.abs( flr.x ) * r;
    flr.y = flr.y / Math.abs( flr.y ) * r;
    flr.z = flr.z / Math.abs( flr.z ) * r;

    //print( "[ ( {ful.x} {fur.x} {fll.x} {flr.x} ) ]" );
    //print( "[ ( {ful.x/Math.abs(ful.x)} {fur.x/Math.abs(fur.x)} ) ]" );

    if( ful.x/Math.abs(ful.x) < 0 ){
        if( fur.x/Math.abs(fur.x) > 0 ){
            frontface = 1;
        }else{
            frontface = 2;
        }
    }else if( ful.x/Math.abs(ful.x) > 0 ){
        if( fur.x/Math.abs(fur.x) > 0 ){
            frontface = 4;
        }else{
            frontface = 3;
        }
    }

    //println( ":{frontface}");
}

// finding which side is facing the top
function closestZmagnitude() {

    var ar = [ f1.z, f2.z, f3.z, f4.z, f5.z, f6.z ];

    var max = -999.9;
    var max_id = -1;
    def nbsp = " ";

    for (i in [1 .. ar.size()]) {
        var f = ar.get(i);
        if( max < f ){
            max_id = i;
            max = f;
        }
    }

    return max;
}


function rescale( additive ){
    r = r + additive as Integer;

    ful.x = ful.x / Math.abs( ful.x ) * r;
    ful.y = ful.y / Math.abs( ful.y ) * r;
    ful.z = ful.z / Math.abs( ful.z ) * r;

    fur.x = fur.x / Math.abs( fur.x ) * r;
    fur.y = fur.y / Math.abs( fur.y ) * r;
    fur.z = fur.z / Math.abs( fur.z ) * r;

    fll.x = fll.x / Math.abs( fll.x ) * r;
    fll.y = fll.y / Math.abs( fll.y ) * r;
    fll.z = fll.z / Math.abs( fll.z ) * r;

    flr.x = flr.x / Math.abs( flr.x ) * r;
    flr.y = flr.y / Math.abs( flr.y ) * r;
    flr.z = flr.z / Math.abs( flr.z ) * r;
}


function playall(){
    f1.play();
    f2.play();
    f6.play();
    f5.play();
}

package function runCube(){
    if( Layout.mode == Layout.MODE_INIT ){
        Layout.switchLayout( Layout.MODE_RUNNING );

        for(i in [1..10000]){}; //spin loop for adjusting

        playall();
    }
}

